#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>        // 包含 struct inode 和 struct file 定义
#include <linux/cdev.h>      // 字符设备相关
#include <linux/uaccess.h>   // 包含 copy_from_user 声明
#include <linux/device.h>   // 包含 class_create 和 device_create 的声明
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/io.h>

//定义字符设备的设备号
static dev_t devno;
#define DEV_NAME "EmbedLedDev"
#define DEV_CNT (1)

static struct platform_device_id led_pdev_ids[] = 
{
    {.name = "led_pdev"},
	{}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);

struct led_data 
{
    unsigned int led_pin;
	void __iomem *gpioc_va_base;
	void __iomem *gpioc_va_out;    
	void __iomem *gpioc_va_enb;
	void __iomem *gpioc_va_fn0;	
	void __iomem *gpioc_va_fn1;	

    struct cdev led_cdev;
};

static int led_cdev_open(struct inode *inode, struct file *filp)
{
    unsigned int val = 0;
    struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);

    printk("led_cdev open \n");

    // 设置做为GPIO来使用
    val = readl(cur_led->gpioc_va_fn0);
    val |= ((1<<14));
    writel(val,cur_led->gpioc_va_fn0);

    //设置GPIOC为输出模式
	val = readl(cur_led->gpioc_va_enb);
	val |= (1<<7); 
	writel(val,cur_led->gpioc_va_enb);
	
	//默认设置为灭
	val = readl(cur_led->gpioc_va_out);
	val |= (1<<7);
	writel(val,cur_led->gpioc_va_out);

    filp->private_data = cur_led;

	printk("led_cdev open ok\n");
    return 0;
}

static int led_cdev_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
                size_t count, loff_t * ppos)
{
    unsigned long val = 0;
    unsigned long ret = 0;

    int tmp = count;

    struct led_data *cur_led = (struct led_data *)filp->private_data;

    val = kstrtoul_from_user(buf, tmp, 10, &ret);

    val = readl(cur_led->gpioc_va_out);
    if (ret == 0)
    {
        val |= (1<<7);
    }
    else
    {
        val &= ~(1<<7);
    }
    writel(val, cur_led->gpioc_va_out);

    //*ppos += tmp;

    return tmp;
}

static struct file_operations led_cdev_fops = {
    .open = led_cdev_open,
    .release = led_cdev_release,
    .write = led_cdev_write,

};


static struct class *led_test_class = NULL;
static int led_pdrv_probe(struct platform_device *pdev)
{
    struct led_data *cur_led;
    unsigned int *led_hwinfo;

    struct resource *gpioc_va_base;
    struct resource *gpioc_va_out;
	struct resource *gpioc_va_enb;
	struct resource *gpioc_va_fn0;
	struct resource *gpioc_va_fn1;
    
    dev_t cur_dev;

    int ret = 0;

    printk("led platform driver probe\n");

    //第一步：提取平台设备提供的资源
    //devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
    cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
    if(!cur_led)
        return -ENOMEM;
    led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int), GFP_KERNEL);
    if(!led_hwinfo)
        return -ENOMEM;

    /* get the pin for led and the reg's shift */
    //dev_get_platdata函数获取私有数据，得到LED灯的寄存器偏移量，并赋值给cur_led->led_pin
    led_hwinfo = dev_get_platdata(&pdev->dev);

    cur_led->led_pin = led_hwinfo[0];
    /* get platform resource */
    //利用函数platform_get_resource可以获取到各个寄存器的地址
    gpioc_va_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    gpioc_va_out  = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	gpioc_va_enb  = platform_get_resource(pdev, IORESOURCE_MEM, 2);
	gpioc_va_fn0  = platform_get_resource(pdev, IORESOURCE_MEM, 3);
	gpioc_va_fn1  = platform_get_resource(pdev, IORESOURCE_MEM, 4);

    //使用devm_ioremap将获取到的寄存器地址转化为虚拟地址
    cur_led->gpioc_va_base = devm_ioremap(&pdev->dev, gpioc_va_base->start, resource_size(gpioc_va_base));
    cur_led->gpioc_va_out  = devm_ioremap(&pdev->dev, gpioc_va_out->start, resource_size(gpioc_va_out));
	cur_led->gpioc_va_enb = devm_ioremap(&pdev->dev, gpioc_va_enb->start, resource_size(gpioc_va_enb));
    cur_led->gpioc_va_fn0  = devm_ioremap(&pdev->dev, gpioc_va_fn0->start, resource_size(gpioc_va_fn0));
	cur_led->gpioc_va_fn1  = devm_ioremap(&pdev->dev, gpioc_va_fn1->start, resource_size(gpioc_va_fn1));


    //第二步：注册字符设备
	#define DEV_MAJOR  200 
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
	//cur_dev = MKDEV(MAJOR(pdev->id), MINOR(pdev->id));

    register_chrdev_region(cur_dev, 1, "led_cdev");

    cdev_init(&cur_led->led_cdev, &led_cdev_fops);

    ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
    if(ret < 0)
    {
        printk("fail to add cdev\n");
        goto add_err;
    }
#define DEV_NAME "EmbedLedDev"
    device_create(led_test_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);

    /* save as drvdata */
    //platform_set_drvdata函数，将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
    platform_set_drvdata(pdev, cur_led);

    return 0;

add_err:
	unregister_chrdev_region(cur_dev, 1);
	return ret;
}

static int led_pdrv_remove(struct platform_device *pdev)
{
    dev_t cur_dev;
    //platform_get_drvdata，获取当前LED灯对应的结构体
    struct led_data *cur_data = platform_get_drvdata(pdev);

    printk("led platform driver remove\n");
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
    //cdev_del删除对应的字符设备
    cdev_del(&cur_data->led_cdev);
    //删除/dev目录下的设备
    device_destroy(led_test_class, cur_dev);
    //unregister_chrdev_region， 注销掉当前的字符设备编号
    unregister_chrdev_region(cur_dev, 1);

    return 0;
}


static struct platform_driver led_pdrv = {

    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver.name = "led_pdev",
    .id_table = led_pdev_ids,
};

static __init int led_pdrv_init(void)
{
	int ret;
    printk("led platform driver init\n");
	
	ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0) 
	{
		printk("fail to alloc devno\n");
		return 0;
	}
	
    //class_create，来创建一个led类
    led_test_class = class_create(THIS_MODULE, "test_leds");
    //调用函数platform_driver_register，注册我们的平台驱动结构体，这样当加载该内核模块时， 就会有新的平台驱动加入到内核中。 第20-27行，注销
    platform_driver_register(&led_pdrv);

    return 0;
}
module_init(led_pdrv_init);

static __exit void led_pdrv_exit(void)
{
    printk("led platform driver exit\n");
    platform_driver_unregister(&led_pdrv);
    class_destroy(led_test_class);
}
module_exit(led_pdrv_exit);

MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");